package com.haoyun.mirage.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.haoyun.common.config.FilterUrlsPropertiesConfig;
import com.haoyun.common.constant.CommonConstant;
import com.haoyun.common.constant.SecurityConstants;
import com.haoyun.common.core.Result;
import com.haoyun.common.core.http.BaseHttpStatus;
import com.haoyun.common.exception.ValidateCodeException;
import com.xiaoleilu.hutool.io.IoUtil;
import com.xiaoleilu.hutool.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.stereotype.Component;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.List;

/**
 * @author lengleng
 * @date 2017-12-18
 * 验证码校验，true开启，false关闭校验
 * 更细化可以 clientId 进行区分
 */
@Slf4j
@Component("validateCodeFilter")
public class ValidateCodeFilter extends OncePerRequestFilter {
    private static final String EXPIRED_CAPTCHA_ERROR = "验证码已过期，请重新获取";

    @Value("${security.validate.code:true}")
    private boolean isValidate;

    @Value("${security.client.clientId}")
    private String clientId;


    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private PathMatcher pathMatcher;

    @Autowired
    private FilterUrlsPropertiesConfig filterUrlsPropertiesConfig;

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        /*List<String> urls = filterUrlsPropertiesConfig.getAnon();
        for (String url : urls) {
            if (pathMatcher.match(url, request.getRequestURI())) {
                return true;
            }
        }*/
        return false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        if (isValidate && checkClientIdValidateCodeIsRequired(request) && (StringUtils.contains(request.getRequestURI(), SecurityConstants.OAUTH_TOKEN_URL)
                || StringUtils.contains(request.getRequestURI(), SecurityConstants.REFRESH_TOKEN)
                || StringUtils.contains(request.getRequestURI(), SecurityConstants.MOBILE_TOKEN_URL))) {
            PrintWriter printWriter = null;
            try {
                checkCode(request, response, filterChain);
            } catch (ValidateCodeException e) {
                log.error("登录失败：{}", e);
                response.setCharacterEncoding(CommonConstant.UTF8);
                response.setContentType(CommonConstant.CONTENT_TYPE);
                response.setStatus(BaseHttpStatus.SC_BAD_REQUEST);
                printWriter = response.getWriter();
                printWriter.write(JSON.toJSONString(Result.buildErrorResult(BaseHttpStatus.SC_BAD_REQUEST, e.getMessage())));
            } finally {
                IoUtil.close(printWriter);
            }
        } else {
            filterChain.doFilter(request, response);
        }
    }

    private boolean checkClientIdValidateCodeIsRequired(HttpServletRequest request){
        String header = request.getHeader(CommonConstant.REQ_HEADER);
        if (header == null || !header.startsWith(CommonConstant.CLIENT_SPLIT)) {
            return false;
        }
        String username = "";
        try {
            String[] tokens = extractAndDecodeHeader(header);
            username = tokens[0];
            if (StrUtil.containsAny(username, StrUtil.splitToArray(clientId, StrUtil.C_COMMA))) {
                return true;
            }
        } catch (ValidateCodeException e) {
            if (log.isDebugEnabled()) {
                log.debug("Check ClientId header found for username '{}'", username);
            }
        }
        return false;
    }

    private void checkCode(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws IOException, ServletException {
        String code = httpServletRequest.getParameter("code");
        if (StringUtils.isBlank(code)) {
            throw new ValidateCodeException("请输入验证码");
        }

        String randomStr = httpServletRequest.getParameter("randomStr");
        if (StringUtils.isBlank(randomStr)) {
            randomStr = httpServletRequest.getParameter("mobile");
        }

        String key = String.format("%s:%s", SecurityConstants.DEFAULT_CODE_KEY, randomStr);
        if (!redisTemplate.hasKey(key)) {
            throw new ValidateCodeException(EXPIRED_CAPTCHA_ERROR);
        }

        Object codeObj = redisTemplate.opsForValue().get(key);

        if (codeObj == null) {
            throw new ValidateCodeException(EXPIRED_CAPTCHA_ERROR);
        }

        String saveCode = codeObj.toString();
        if (StringUtils.isBlank(saveCode)) {
            redisTemplate.delete(key);
            throw new ValidateCodeException(EXPIRED_CAPTCHA_ERROR);
        }

        if (!StringUtils.equals(saveCode, code)) {
            redisTemplate.delete(key);
            throw new ValidateCodeException("验证码错误，请重新输入");
        }

        redisTemplate.delete(key);
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }


    private String[] extractAndDecodeHeader(String header) throws ValidateCodeException {
        byte[] base64Token = StringUtils.substringAfter(header,CommonConstant.CLIENT_SPLIT).getBytes(Charset.defaultCharset());
        byte[] decoded;
        try {
            decoded = Base64.decode(base64Token);
        } catch (IllegalArgumentException e) {
            throw new ValidateCodeException(
                    "Failed to decode basic authentication token");
        }
        String token = new String(decoded, Charset.defaultCharset());
        int delim = token.indexOf(":");
        if (delim == -1) {
            throw new ValidateCodeException("Invalid basic authentication token");
        }
        return new String[]{token.substring(0, delim), token.substring(delim + 1)};
    }
}
